package edu.njit.hz278.core.web;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;

/**
 * 
 * @author Rivers Zhang
 *
 */
public abstract class AbstractInvoker implements Invoker {

	/**
	 * Converts @value to @type.
	 * 
	 * @param type
	 * @param value
	 * @return
	 */
	protected static Object getPrimitiveValue(Class<?> type, String value) {

		// String is not a primitive type. add this for simplicity

		boolean isEmpty = (value == null || value.length() == 0);

		if (type == String.class)
			return isEmpty ? "" : value;

		if (type == int.class)
			return isEmpty ? 0 : new Integer(value);

		if (type == short.class)
			return isEmpty ? 0 : new Short(value);

		if (type == long.class)
			return isEmpty ? 0l : new Long(value);

		if (type == double.class)
			return isEmpty ? 0d : new Double(value);

		if (type == float.class)
			return isEmpty ? 0f : new Float(value);

		if (type == char.class)
			return isEmpty ? 0 : value.charAt(0);

		if (type == byte.class)
			return isEmpty ? 0 : new Byte(value);

		if (type == boolean.class)
			return isEmpty || value.equals("0")
					|| value.equalsIgnoreCase("false") ? Boolean.FALSE
					: Boolean.TRUE;

		log.warn("Type " + type.getName() + " cannot be binded automatically.");
		return null;
	}

	/**
	 * Converts @values to an array of instances of @type.
	 * 
	 * @param type
	 * @param values
	 * @return
	 */
	protected static Object getPrimitiveValues(Class<?> type, String[] values) {

		if (values == null)
			return null;

		int len = values.length;

		// String is not a primitive type. add this for simplicity
		if (type == String.class)
			return values;

		if (type == int.class) {
			int[] ret = new int[len];
			for (int i = 0; i < len; ++i) {
				String value = values[i];
				ret[i] = value.length() == 0 ? 0 : new Integer(values[i]);
			}
			return ret;
		}

		if (type == short.class) {
			short[] ret = new short[len];
			for (int i = 0; i < len; ++i) {
				String value = values[i];
				ret[i] = value.length() == 0 ? 0 : new Short(values[i]);
			}
			return ret;
		}

		if (type == long.class) {
			long[] ret = new long[len];
			for (int i = 0; i < len; ++i) {
				String value = values[i];
				ret[i] = value.length() == 0 ? 0L : new Long(values[i]);
			}
			return ret;
		}

		if (type == double.class) {
			double[] ret = new double[len];
			for (int i = 0; i < len; ++i) {
				String value = values[i];
				ret[i] = value.length() == 0 ? 0d : new Double(values[i]);
			}
			return ret;
		}

		if (type == float.class) {
			float[] ret = new float[len];
			for (int i = 0; i < len; ++i) {
				String value = values[i];
				ret[i] = value.length() == 0 ? 0f : new Float(values[i]);
			}
			return ret;
		}

		if (type == char.class) {
			char[] ret = new char[len];
			for (int i = 0; i < len; ++i) {
				String value = values[i];
				ret[i] = value.length() == 0 ? 0 : value.charAt(0);
			}
			return ret;
		}

		if (type == byte.class) {
			byte[] ret = new byte[len];
			for (int i = 0; i < len; ++i) {
				String value = values[i];
				ret[i] = value.length() == 0 ? 0 : new Byte(value);
			}
			return ret;
		}

		if (type == boolean.class) {

			boolean[] ret = new boolean[len];
			for (int i = 0; i < len; ++i) {
				String value = values[i];
				if (!value.equals("0") || value.toLowerCase().equals("true"))
					ret[i] = Boolean.TRUE;
				else
					ret[i] = Boolean.FALSE;
			}
			return ret;
		}

		log.warn("Type " + type.getName() + " cannot be binded automatically.");
		return null;

	}

	/**
	 * Creates a single java bean using Jackson Json processor.
	 * 
	 * @param type
	 * @param value
	 * @return
	 * @throws JsonParseException
	 * @throws JsonMappingException
	 * @throws IOException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	protected Object createBean(Class<?> type, String value)
			throws JsonParseException, JsonMappingException, IOException,
			InstantiationException, IllegalAccessException {
		return value == null || value.length() == 0 ? type.newInstance()
				: JSONMapper.decode(type, value);
	}

	/**
	 * Creates a list of instances of @type, using Jackson JSON processor.
	 * 
	 * @param type
	 * @param value
	 * @return
	 * @throws JsonParseException
	 * @throws JsonMappingException
	 * @throws IOException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	protected Object createBeans(Class<?> type, String value)
			throws JsonParseException, JsonMappingException, IOException,
			InstantiationException, IllegalAccessException {
		return value == null || value.length() == 0 ? Array
				.newInstance(type, 0) : JSONMapper.decodeAsArray(type, value);
	}

	public AbstractInvoker(Object target, Method method, String[] paramNames) {

		this.paramTypes = method.getParameterTypes();
		this.paramNames = paramNames;
		this.target = target;
		this.method = method;
		this.paramLen = paramTypes.length;

		if (method.getAnnotation(JSONP.class) != null)
			this.writer = new JSONPWriter();
		else if (method.getReturnType().equals(String.class))
			this.writer = new PageWriter();
		else
			this.writer = new JSONWriter();
	}

	protected Class<?>[] paramTypes;
	protected String[] paramNames;
	protected int paramLen;
	protected Object target;
	protected Method method;
	protected Writer writer;

	protected static final Logger log = LogManager.getLogger(AbstractInvoker.class);
}
